Support Microsoft.Build.Traversal projects in dotnet test (MTP)#55297
Conversation
Unblocks traversal (dirs.proj) support for 'dotnet test' with Microsoft.Testing.Platform (issue #51316). Three coordinated changes mirror how solutions are handled - the traversal project is a container that forwards to its ProjectReference items: - CLI evaluation: special-case the 'IsTraversal' MSBuild property in SolutionAndProjectUtility.GetProjectProperties. When a project is a traversal project, expand it into its resolved ProjectReference items and evaluate each recursively (so nested traversal projects work). - CLI arg parsing: recognize any '*proj' extension (not just .csproj/.vbproj/.fsproj) as a positional project in MSBuildUtility.GetPositionalArguments, matching ValidateProjectOrSolutionPath. Without this, 'dotnet test dirs.proj' leaked 'dirs.proj' to the test host as an invalid argument (MTP exit code 5). - Targets: the _MTPBuild target now forwards to @(ProjectReference) when IsTraversal is true, so the referenced test projects actually build. Adds a TraversalTestProjects test asset and RunTraversalProject_ShouldRunReferencedTestProjects test.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
…ata for traversal - De-duplicate referenced projects across the entire traversal graph (not just within a single traversal file) by threading a visited-set through the recursion. This prevents a project referenced by multiple traversal projects (a diamond) from being tested twice and guards against reference cycles. - Honor per-reference Configuration/Platform metadata on ProjectReference items, falling back to the values inherited from the traversal project. - Add a nested/diamond test asset (TraversalTestProjectsNested) and RunNestedTraversalProjectWithDiamond_ShouldRunSharedProjectOnce. The test apps drop a unique marker file per launch so the test deterministically asserts the shared project runs exactly once and the leaf-only project (reachable solely via nested traversal) also runs once.
|
@baronfel given there is no ETA or news for when transversal APIs will be available, I went ahead with doing some "hacks" as a simplified version which should give us a first support for |
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds draft traversal-project (Microsoft.Build.Traversal / dirs.proj) support to dotnet test when using Microsoft.Testing.Platform by expanding traversal containers into their referenced projects for evaluation and ensuring referenced projects get built.
Changes:
- Teach MTP project evaluation to detect traversal projects and recursively expand their
ProjectReferencegraph (with de-dup/cycle-guard). - Update argument parsing to treat any
*projfile as a positional project so it isn’t forwarded to the test host. - Add MTP traversal test assets + tests, and update
_MTPBuildto forward to@(ProjectReference)for traversal projects.
Show a summary per file
| File | Description |
|---|---|
| src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs | Detects traversal projects and recursively evaluates referenced projects (incl. de-dup). |
| src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs | Broadens positional project detection to any extension ending in proj. |
| src/Cli/dotnet/Commands/Test/CliConstants.cs | Adds MSBuild property/item names used for traversal support. |
| src/Layout/redist/MSBuildImports/Current/Microsoft.Common.targets/ImportAfter/Microsoft.TestPlatform.ImportAfter.targets | For traversal projects, forwards _MTPBuild to referenced projects. |
| test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs | Adds tests covering traversal execution, nesting, and diamond de-dup. |
| test/TestAssets/TestProjects/TraversalTestProjects/global.json | Configures test assets to use Microsoft.Testing.Platform runner. |
| test/TestAssets/TestProjects/TraversalTestProjects/dirs.proj | Simple traversal project referencing two MTP test apps. |
| test/TestAssets/TestProjects/TraversalTestProjects/TestProject/TestProject.csproj | MTP “test app” project used by traversal asset. |
| test/TestAssets/TestProjects/TraversalTestProjects/TestProject/Program.cs | Minimal dummy MTP adapter app for traversal tests. |
| test/TestAssets/TestProjects/TraversalTestProjects/OtherTestProject/OtherTestProject.csproj | Second MTP “test app” project used by traversal asset. |
| test/TestAssets/TestProjects/TraversalTestProjects/OtherTestProject/Program.cs | Minimal dummy MTP adapter app for traversal tests. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/global.json | Configures nested traversal assets to use Microsoft.Testing.Platform runner. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/dirs.proj | Top-level traversal with nested traversal and a diamond reference. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/sub/dirs.proj | Nested traversal referencing shared + leaf MTP projects. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/SharedTestProject/SharedTestProject.csproj | Shared MTP “test app” used to validate diamond de-dup. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/SharedTestProject/Program.cs | Drops marker files to deterministically count runs for de-dup validation. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/LeafTestProject/LeafTestProject.csproj | Leaf-only MTP “test app” reachable only through nested traversal. |
| test/TestAssets/TestProjects/TraversalTestProjectsNested/LeafTestProject/Program.cs | Drops marker files to deterministically count runs for recursion validation. |
Copilot's findings
- Files reviewed: 18/18 changed files
- Comments generated: 3
…targets metadata forwarding Addresses three Copilot review comments on #55297: - Resolve ProjectReference 'FullPath' relative to the traversal project directory via Path.GetFullPath(fullPath, projectDirectory) instead of the process working directory. - Include Configuration/Platform in the traversal de-duplication key so the same project referenced with different Configuration/Platform is tested for each distinct combination, while true diamonds (same combination) are still de-duplicated. Introduces GetTraversalVisitKey and keys the traversal project's own visited entry the same way. - _MTPBuild now forwards per-reference Configuration/Platform metadata as global properties, consistent with the evaluation logic. Property assignments are precomputed in an ItemGroup so empty metadata never emits 'Configuration=' (which would clear the inherited global property); empty segments in Properties are ignored by MSBuild.
The GetProjectProperties signature gained a FacadeLogger? parameter on main. Thread it through the recursive traversal call to resolve CS1503.
|
/ba-g CI analysis: the failing check is unrelated flakiness (not this PR)The only failing leg on the latest run (build 1517459) is Failing test
Root cause (two combining issues)
Already tracked as a Known Build Error: #55258, #55044, #53289. Suggested fixes (belong in a separate dotnet-watch test-infra PR, not here)
@tmat does this match your understanding of the WebSocket hot-reload hang? Happy to open the test-infra timeout fix separately if useful. |
|
/backport to release/11.0.1xx-preview7 |
|
Started backporting to |
|
@Evangelink backporting to git am output$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Applying: Draft: support Microsoft.Build.Traversal projects in dotnet test (MTP)
Applying: Add cross-graph de-dup and per-reference Configuration/Platform metadata for traversal
Using index info to reconstruct a base tree...
M src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs
M test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs
Auto-merging test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs
CONFLICT (content): Merge conflict in test/dotnet.Tests/CommandTests/Test/GivenDotnetTestBuildsAndRunsTests.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0002 Add cross-graph de-dup and per-reference Configuration/Platform metadata for traversal
Error: The process '/usr/bin/git' failed with exit code 128 |
Summary
Draft / proof-of-concept that unblocks traversal project (
dirs.proj) support fordotnet testwith Microsoft.Testing.Platform. Fixes the scenario in #51316.The approach treats a
Microsoft.Build.Traversalproject as a container that forwards to itsProjectReferenceitems — the same mental model as a solution.Root cause
Two independent problems both had to be fixed (a third surfaced while testing):
_MTPBuildtarget (Microsoft.TestPlatform.ImportAfter.targets) only builds$(MSBuildProjectFullPath)whenIsTestingPlatformApplication == true. A traversal project is not an MTP app, so nothing (including its referenced test projects) was built..projwas not a recognized positional project extension, sodotnet test dirs.projleakeddirs.projto the test host as an invalid argument (MTP exit code5).Changes
SolutionAndProjectUtility.csIsTraversalproperty. When a project is a traversal project, expand it into its resolvedProjectReferenceitems and evaluate each recursively (nested traversal). De-duplicates across the whole traversal graph (diamond references / cycle guard) via a threaded visited-set, and honors per-referenceConfiguration/Platformmetadata (falling back to inherited values).MSBuildUtility.cs*projextension as a positional project (mirrorsValidateProjectOrSolutionPath), so the traversal path isn't forwarded to the test host as an argument.CliConstants.csIsTraversal/ProjectReferencenames.Microsoft.TestPlatform.ImportAfter.targets_MTPBuildnow forwards_MTPBuildto@(ProjectReference)whenIsTraversal == true, so the referenced test projects actually build.TraversalTestProjects/+ testdirs.proj+ two MTP apps) andRunTraversalProject_ShouldRunReferencedTestProjects.TraversalTestProjectsNested/+ testRunNestedTraversalProjectWithDiamond_ShouldRunSharedProjectOnce. Test apps drop a unique marker file per launch so the test deterministically asserts the shared (diamond) project runs exactly once and the leaf-only project (reachable solely via nested traversal) also runs — proving recursion + de-dup.Validation
Notes / open questions for reviewers
ProjectReferenceitems from evaluation are sufficient, and MSBuild already expands globs/conditions during evaluation. A cleaner long-term design would want a public traversal API to get the effective project set (analogous toSolutionFile.ProjectsInOrder) rather than relying on rawProjectReferenceitems — similar in spirit to the existingProjectShouldBuildUnsafeAccessorworkaround used for solutions (see MSBuild should publish a library that helps tools interact with Solution files and other container-ish project types msbuild#12711).Configuration/Platform; the P2PSet*/AdditionalPropertiesforms are not yet parsed (theSet*form uses aName=Valuestring that would need parsing)._MTPBuildforwarding is intentionally minimal for this draft (no explicitBuildInParallel/property forwarding). The cross-targeting_MTPBuildvariant is left unchanged since traversal projects are not cross-targeting.Closes #51316 (once finalized).